#ifndef __REDCELL_MARSHAL_HPP__
#define __REDCELL_MARSHAL_HPP__

#include <iterator>
#include <vector>
#include <cassert>
#include "common.hpp"


namespace redcell {

  template <typename OutputIterator>
  struct OutputBuffer {
    typedef std::vector<char> CharVec;

    OutputIterator cur;
    unsigned char flag;
    CharVec data;
    size_t level;

    OutputBuffer(OutputIterator begin):
      cur(begin), flag(0), level(1) {
    }

  private:

    void putFlag(const unsigned char val) {
      assert(!(val & 0x10));
      if (flag) {
	*cur++ = flag | (val << 4);
	std::copy(data.begin(), data.end(), cur);
	data.clear();
	flag = 0;
      } else if (level) {
	flag = val;
      } else {
	*cur++ = val | (F_PADDING << 4);
	level = 1;
      }
    }	


    void put(const char ch) {
      if (flag) {
	data.push_back(ch);
      } else {
	*cur++ = ch;
      }
    }

    template <typename Iterator>
    void put(Iterator begin, Iterator end) {
      if (flag) {
	while (begin != end) {
	  data.push_back(*begin++);
	}
      } else {
	std::copy(begin, end, cur);
      }
    }

    //

    void putEncodedInt(Integer val) {
      Integer empty = val >> (sizeof(Integer) * 8 - 1);
      while (true) {
	char ch = (char)val;
	if ((val >>= 7) == empty) {
	  put(ch | 0x80);
	  break;
	} else {
	  put(ch & 0x7f);
	}
      }
    }

  public:

    void putKeySkip(const size_t val) {
      putFlag(F_KEYSKIP);
      putEncodedInt(val);
    }

    void putNil() {
      putFlag(F_NIL);
    }
    
    void putInteger(const Integer val) {
      if (val == FALSE) {
        putFlag(F_FALSE);
      } else if (val == TRUE) {
        putFlag(F_TRUE);
      } else {
        putFlag(val >= 0 ? F_POS : F_NEG);
        putEncodedInt(val);
      }
    }

    void putReal(const Real val) {
      float single = (float)val;
      size_t len;
      uint64_t ival;
      if (single == val) {
	putFlag(F_FLOAT);
	ival = reinterpret_cast<const uint32_t &>(single);
	len = sizeof(float);
      } else {
	putFlag(F_DOUBLE);
	ival = reinterpret_cast<const uint64_t &>(val);
	len = sizeof(double);
      }
      while (len--) {
	put((char)ival);
	ival >>= 8;
      }
    }

    void putString(const String &val) {
      putFlag(F_STR);
      putEncodedInt(val.size());
      put(val.begin(), val.end());
    }

    void putEmptySub() {
      putFlag(F_EMPTYSUB);
    }

    void putSub() {
      putFlag(F_SUB);
      ++level;
    }

    void putEnd() {
      --level;
      putFlag(F_END);
    }

  };


  template <typename InputIterator>
  struct InputBuffer {
    InputIterator cur;
    InputIterator end;
    unsigned char flag;

    InputBuffer(InputIterator begin, InputIterator end)
      : cur(begin), end(end), flag(0) {
    }

  private:

    void get(char &ch) {
      if (cur == end) {
	throw MarshalError("Unexpected end of stream");
      }
      ch = *cur++;
    }

  public:

    unsigned char getFlag() {
      unsigned char res;
      if (flag && flag != F_PADDING) {
	res = flag;
	flag = 0;
      } else {
	get(reinterpret_cast<char &>(flag));
	res = flag & 0x0f;
	flag >>= 4;
      }
      return res;
    }


    Integer getInteger(const int sign) {
      Integer val = 0;
      int shift = 0;
      char ch;
      do {
	get(ch);
	val |= (Integer)(ch & 0x7f) << shift;
	shift += 7;
      } while (!(ch & 0x80));
      return val | ((Integer)sign << shift);
    }

    Real getFloat() {
      uint32_t buf = 0;
      for (int i = 0; i < 32; i += 8) {
	char ch;
        get(ch);
        buf |= (uint32_t)(unsigned char)ch << i;
      }
      return reinterpret_cast<float &>(buf);
    }

    Real getDouble() {
      uint64_t buf = 0;
      for (int i = 0; i < 64; i += 8) {
	char ch;
        get(ch);
        buf |= (uint64_t)(unsigned char)ch << i;
      }
      return reinterpret_cast<double &>(buf);
    }

    String getString() {
      size_t size = getInteger(0);
      String res;
      res.reserve(size);
      while (size--) {
	char ch;
	get(ch);
	res.push_back(ch);
      }
      return res;
    }

  };

}

#endif
